<?php

namespace GanYiInfo\HttpClient;

use GanYiInfo\Http\RawResponse;

class Curl implements HttpClientInterface
{
    /**
     * Default curl options
     *
     * These defaults options can be overwritten when sending requests.
     *
     * See setCurlOptions()
     *
     * @var array
     */
    protected $curlOptions = [
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 5,
        CURLINFO_HEADER_OUT => true,
        CURLOPT_ENCODING => 'identity'
    ];

    /**
     * Method request() arguments
     *
     * This is used for debugging.
     *
     * @var array
     */
    protected $requestArguments = [];

    /**
     * Default request headers
     *
     * @var array
     */
    protected $requestHeader = [
        'Accept' => '*/*',
        'Cache-Control' => 'max-age=0',
        'Connection' => 'keep-alive',
        'Content-Type' => 'application/json',
        'Expect' => '',
        'Pragma' => '',
    ];

    /**
     * Headers returned in the response
     *
     * @var array
     */
    protected $responseHeader = [];

    /**
     * Response HTTP status code
     *
     * @var integer
     */
    protected $responseHttpCode = 0;

    /**
     * Last curl error number
     *
     * @var mixed
     */
    protected $responseClientError = null;

    /**
     * Information about the last transfer
     *
     * @var mixed
     */
    protected $responseClientInfo = [];

    /**
     * {@inheritdoc}
     */
    public function request($uri, $method = 'GET', $body = "", $headers = [], $timeout = 60)
    {
        $this->requestHeader = array_replace($this->requestHeader, (array)$headers);

        $this->requestArguments = [
            'uri' => $uri,
            'method' => $method,
            'parameters' => $body,
            'headers' => $this->requestHeader,
        ];

        $curl = curl_init();

        $this->curlOptions[CURLOPT_POSTFIELDS] = $body;

        if ('GET' == $method) {
            unset($this->curlOptions[CURLOPT_POST]);
            unset($this->curlOptions[CURLOPT_POSTFIELDS]);
        }

        if ('POST' == $method) {
            $this->curlOptions[CURLOPT_POST] = true;
        }

        if ('PUT' == $method) {
            $this->curlOptions[CURLOPT_CUSTOMREQUEST] = 'PUT';
        }

        $this->curlOptions[CURLOPT_TIMEOUT] = $timeout;
        $this->curlOptions[CURLOPT_CONNECTTIMEOUT] = $timeout;
        $this->curlOptions[CURLOPT_URL] = $uri;
        $this->curlOptions[CURLOPT_HTTPHEADER] = $this->prepareRequestHeaders();
        $this->curlOptions[CURLOPT_HEADERFUNCTION] = [$this, 'fetchResponseHeader'];

        foreach ($this->curlOptions as $opt => $value) {
            curl_setopt($curl, $opt, $value);
        }

        $response = curl_exec($curl);
        $this->responseHttpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        $this->responseClientError = curl_error($curl);
        $this->responseClientInfo = curl_getinfo($curl);

        curl_close($curl);

        return new RawResponse($this->getResponseHeader(), $response);
    }

    /**
     * Reset curl options
     *
     * @param array $curlOptions
     */
    public function setCurlOptions($curlOptions)
    {
        if (is_array($curlOptions)) {
            foreach ($curlOptions as $opt => $value) {
                $this->curlOptions[$opt] = $value;
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getResponseHeader()
    {
        return $this->responseHeader;
    }

    /**
     * {@inheritdoc}
     */
    public function getResponseHttpCode()
    {
        return $this->responseHttpCode;
    }

    /**
     * {@inheritdoc}
     */
    public function getResponseClientError()
    {
        return $this->responseClientError;
    }

    /**
     * @return array
     */
    protected function getResponseClientInfo()
    {
        return $this->responseClientInfo;
    }

    /**
     * Returns method request() arguments
     *
     * This is used for debugging.
     *
     * @return array
     */
    protected function getRequestArguments()
    {
        return $this->requestArguments;
    }

    /**
     * Fetch server response headers
     *
     * @param mixed $curl
     * @param string $header
     *
     * @return integer
     */
    protected function fetchResponseHeader($curl, $header)
    {
        $pos = strpos($header, ':');
        if (!empty($pos)) {
            $key = str_replace('-', '_', strtolower(substr($header, 0, $pos)));
            $value = trim(substr($header, $pos + 2));
            $this->responseHeader[$key] = $value;
        }

        return strlen($header);
    }

    /**
     * Convert request headers to the expect curl format
     *
     * @return array
     */
    protected function prepareRequestHeaders()
    {
        $headers = [];
        foreach ($this->requestHeader as $header => $value) {
            $headers[] = trim($header) . ': ' . trim($value);
        }

        return $headers;
    }
}